home *** CD-ROM | disk | FTP | other *** search
/ TeX 1995 July / TeX CD-ROM July 1995 (Disc 1)(Walnut Creek)(1995).ISO / biblio / bibtex / utils / bibclean / match.c < prev    next >
C/C++ Source or Header  |  1992-11-21  |  10KB  |  394 lines

  1. /***********************************************************************
  2.  
  3. ==========
  4. BACKGROUND
  5. ==========
  6.  
  7. This file contains an Implementation of limited regular-expression
  8. pattern matching code.  The pattern syntax is simpler, more limited,
  9. and different from normal regular-expression pattern matching syntax.
  10. It is described in more detail below.
  11.  
  12. The motivation for this new code is that I found considerable
  13. inconsistency in the matching behavior between versions of either
  14. re_comp()/re_exec() or compile()/step() on these systems
  15.  
  16.     DECstation 3100
  17.     IBM 3090
  18.     IBM PS/2
  19.     IBM RS/6000 AIX 3.2
  20.     NeXT Mach 3.0
  21.     Silicon Graphics IRIX 4.0
  22.     Stardent OS 2.2
  23.     Sun SPARC
  24.  
  25. That makes use of those regular-expression pattern matching unreliable
  26. across systems.
  27.  
  28. One possible solution would be to use the GNU re_comp() and re_exec()
  29. from the regexp distribution on prep.ai.mit.edu (as of writing,
  30. pub/gnu/regex-0.11.*).  However, that code is large (5000+ lines), and
  31. its installation uses configuration facilities that only work under
  32. some variants of UNIX, and are completely useless on other operating
  33. systems.
  34.  
  35. By contrast, the pattern matching code here is quite adequate for
  36. bibclean's needs, and can be expressed in fewer than 140 lines.  In
  37. addition, it provides special handling of TeX control sequences and
  38. braces that would be rather awkward to express in conventional
  39. regular-expression syntax.
  40.  
  41. If the symbol TEST is defined at compile time, a main program will be
  42. included that can be used for testing patterns supplied from stdin.
  43.  
  44.  
  45. ==============
  46. PATTERN SYNTAX
  47. ==============
  48.  
  49. The string values to be pattern-matched are tab-free single-line
  50. values delimited by quotation marks.
  51.  
  52. The patterns are represented by the following special markers:
  53.  
  54.     a    exactly one letter
  55.     A    one or more letters
  56.     d    exactly one digit
  57.     D    one or more digits
  58.     w    exactly one word (one or more letters and digits)
  59.     W    one or more space-separated words, beginning and ending
  60.         with a word
  61.     X    one or more special-separated words, beginning and ending
  62.         with a word
  63.     .    one special character (see SPECIAL_CHARS defined below)
  64.     :    one or more special characters
  65.     <space>    one or more spaces
  66.     \x    exactly one x (x is an character)
  67.     x    exactly the character x (x is anything but aAdDwW.:<space>\)
  68.  
  69. Special characters are a subset of punctuation characters that are
  70. typically used in values.
  71.  
  72. Note the \<space> represents a single literal space, \\ a single
  73. literal backslash, \a the letter a, \A the letter A, \d the letter d,
  74. \D the letter D, and so on.  Remember to double all backslashes in C
  75. strings: \a must be entered as \\a, and "and" as "\\an\\d".
  76.  
  77. Each pattern is matched against the entire string and must match
  78. successfully for a YES return from match_pattern().  Consequently,
  79. there is no need for an analogue of ^ and $ in full regular
  80. expressions.  Neither is there provision for matching on arbitrary
  81. sets of characters.  Instead, fixed sets of characters are provided
  82. (conventional regular-expression equivalents are shown in
  83. parentheses):
  84.  
  85.     digits ([0-9]),
  86.     alphanumerics ([A-Za-z0-9]),
  87.     space ([ \t\f\r\n\v]), and
  88.     special ([][" !#()*+,-./:;?~])
  89.  
  90. In addition, TeX control sequences of the form
  91. \<one-special-character> or \<letter-sequence> in the string are
  92. ignored in the match, together with any following whitespace.
  93.  
  94. Braces are also ignored, but not whitespace following them.
  95.  
  96. Thus "{TR\slash A87}" matches the patterns "AD" and "W", and
  97. "{TR A\slash 87}" matches the patterns "A AD" and "A W".
  98.  
  99. [11-Nov-1992]
  100. ***********************************************************************/
  101.  
  102. #include "os.h"
  103. #include "xstdlib.h"
  104. #include "xstring.h"
  105. #include "xctype.h"
  106.  
  107. RCSID("$Id: match.c,v 1.2 1992/11/22 17:44:32 beebe Exp beebe $")
  108.  
  109. /* $Log: match.c,v $
  110.  * Revision 1.2  1992/11/22  17:44:32  beebe
  111.  * Change type of match_patterns from int to YESorNO for version 2.05 bibclean.
  112.  *
  113.  * Revision 1.1  1992/11/15  08:20:05  beebe
  114.  * Initial revision
  115.  * */
  116.  
  117. #define NEW_STYLE    (__cplusplus || __STDC__ || c_plusplus)
  118.  
  119. #if NEW_STYLE
  120. #define VOID    void
  121. #else /* K&R style */
  122. #define VOID
  123. #endif /* NEW_STYLE */
  124.  
  125. #if NEW_STYLE
  126. typedef enum { NO = 0, YES = 1 } YESorNO;
  127. #else /* K&R style */
  128. #define NO  0            /* must be FALSE (zero) */
  129. #define YES 1            /* must be TRUE (non-zero) */
  130. typedef int YESorNO;
  131. #endif /* NEW_STYLE */
  132.  
  133. #include "match.h"
  134.  
  135. #ifndef EXIT_SUCCESS
  136. #define EXIT_SUCCESS 0
  137. #endif
  138.  
  139. #define SPECIAL_CHARS    " !#()*+,-./:;?[]~"
  140.  
  141. #define isspecial(c)    (strchr(SPECIAL_CHARS,(c)) != (char*)NULL)
  142.  
  143. static const char    *next_s ARGS((const char *s_));
  144.  
  145.  
  146. #if NEW_STYLE
  147. YESorNO
  148. match_pattern(const char *s, const char *pattern)
  149. #else /* K&R style */
  150. YESorNO
  151. match_pattern(s,pattern)
  152. const char *s;
  153. const char *pattern;
  154. #endif /* NEW_STYLE */
  155. {
  156.     s = next_s(s-1);
  157.     for ( ; *pattern; ++pattern)
  158.     {
  159.     switch(*pattern)
  160.     {
  161.     case 'a':            /* single letter */
  162.         if (!isalpha(*s))
  163.         return (NO);
  164.         s = next_s(s);
  165.         break;
  166.  
  167.     case 'w':            /* one word (letters and digits) */
  168.         if (!isalnum(*s))
  169.         return (NO);
  170.         while (isalnum(*s))
  171.         s = next_s(s);
  172.         break;
  173.  
  174.     case 'A':            /* one or more letters */
  175.         if (!isalpha(*s))
  176.         return (NO);
  177.         while (isalpha(*s))
  178.         s = next_s(s);
  179.         break;
  180.  
  181.     case 'd':
  182.         if (!isdigit(*s))        /* single digit */
  183.         return (NO);
  184.         s = next_s(s);
  185.         break;
  186.  
  187.     case 'D':            /* one or more digits */
  188.         if (!isdigit(*s))
  189.         return (NO);
  190.         while (isdigit(*s))
  191.         s = next_s(s);
  192.         break;
  193.  
  194.     case 'W':            /* one or more space-separated words */
  195.         if (!isalnum(*s))
  196.         return (NO);
  197.         while (isalnum(*s))        /* parse first word */
  198.         s = next_s(s);
  199.         for (;;)
  200.         {
  201.         if (!isspace(*s))
  202.             break;
  203.         while (isspace(*s))    /* parse separators */
  204.             s = next_s(s);
  205.         while (isalnum(*s))    /* parse another word */
  206.             s = next_s(s);
  207.         }
  208.         break;
  209.  
  210.     case 'X':        /* one or more special-separated words */
  211.         if (!isalnum(*s))
  212.         return (NO);
  213.         while (isalnum(*s))        /* parse first word */
  214.         s = next_s(s);
  215.         for (;;)
  216.         {
  217.         if (!isspecial(*s))
  218.             break;
  219.         while (isspecial(*s))    /* parse separators */
  220.             s = next_s(s);
  221.         while (isalnum(*s))    /* parse another word */
  222.             s = next_s(s);
  223.         }
  224.         break;
  225.  
  226.     case ' ':            /* one or more whitespace characters */
  227.         if (!isspace(*s))
  228.         return (NO);
  229.         while (isspace(*s))
  230.         s = next_s(s);
  231.         break;
  232.  
  233.     case '.':            /* exactly one special character */
  234.         if (!isspecial(*s))
  235.         return (NO);
  236.         break;
  237.  
  238.     case ':':            /* one or more special characters */
  239.         if (!isspecial(*s))
  240.         return (NO);
  241.         while (isspecial(*s))
  242.         s = next_s(s);
  243.         break;
  244.  
  245.         case '\\':            /* literal next character */
  246.         pattern++;
  247.         /* fall through to exact match test */
  248.  
  249.     default:            /* anything else: exact match */
  250.         if (*pattern != *s)
  251.         return(NO);
  252.         s = next_s(s);
  253.     }                /* end switch */
  254.     }                    /* end for (; ;) */
  255.     return (*s == '\0' ? YES : NO);    /* YES if reached end of string */
  256. }
  257.  
  258.  
  259. #if NEW_STYLE
  260. static const char *
  261. next_s(const char *s)
  262. #else /* K&R style */
  263. static const char *
  264. next_s(s)
  265. const char *s;
  266. #endif /* NEW_STYLE */
  267. {
  268.     /* find next position in s, ignoring braces and ignoring TeX control
  269.        sequences and any space that follows them */
  270.     for (++s; (*s == '\\') || (*s == '{') || (*s == '}') ; )
  271.     {
  272.     switch (*s)
  273.     {
  274.     case '\\':            /* TeX control sequence */
  275.         ++s;            /* look at next character */
  276.         if (isalpha(*s))        /* \<one-or-more-letters> */
  277.         {
  278.         while (isalpha(*s))
  279.             ++s;
  280.         }
  281.         else            /* \<non-letter> */
  282.         ++s;
  283.         while (isspace(*s))        /* advance over trailing whitespace */
  284.             ++s;            /* since TeX does too */
  285.         break;
  286.  
  287.         case '{':
  288.     case '}':
  289.         ++s;
  290.         break;
  291.  
  292.     default:
  293.         return (s);
  294.     }                /* end switch */
  295.     }                    /* end for */
  296.     return (s);
  297. }
  298.  
  299. #ifdef TEST
  300. #define MAXLINE 256
  301.  
  302. #define NO_WARNING    (const char *)NULL
  303.  
  304. MATCH_PATTERN year_patterns[] =
  305. {
  306.     {"\"DDDD\"",        NO_WARNING},
  307.     {"\"DDDD,WDDDD\"",        NO_WARNING},
  308.     {"\"DDDD, DDDD, DDDD\"",    NO_WARNING},
  309.     {(const char*)NULL,        NO_WARNING},
  310. };
  311.  
  312. MATCH_PATTERN number_patterns[] =
  313. {
  314.     {"\"D\"",            "23"},
  315.     {"\"A AD\"",        "PN LPS5001"},
  316.     {"\"A D(D)\"",        "RJ 34(49)"},
  317.     {"\"A D\"",            "XNSS 288811"},
  318.     {"\"A D\\.D\"",        "Version 3.20"},
  319.     {"\"A-A-D-D\"",        "UMIAC-TR-89-11"},
  320.     {"\"A-A-D\"",        "CS-TR-2189"},
  321.     {"\"A-A-D\\.D\"",        "CS-TR-21.7"},
  322.     {"\"A-AD-D\"",        "TN-K\\slash 27-70"},
  323.     {"\"A-D D\"",        "PB-251 845"},
  324.     {"\"A-D-D\"",        "ANL-30-74"},
  325.     {"\"A-D\"",            "TR-2189"},
  326.     {"\"AD-D-D\"",        "GG24-3611-00"},
  327.     {"\"AD-D\"",        "SP43-29"},
  328.     {"\"AD\"",            "LPS0064"},
  329.     {"\"A\\#D-D\"",        "TR\\#89-24 ????"},
  330.     {"\"D \\an\\d D\"",        "11 and 12"},
  331.     {"\"D+D\"",            "3+4"},
  332.     {"\"D-D\"",            "23-27"},
  333.     {"\"D/D\"",            "23/27"},
  334.     {"\"DA\"",            "23A"},
  335.     {"\"D\\.D\"",        "3.4"},
  336.     {"\"W-W W\"",        "AERE-R 12329"},
  337.     {"\"W-W-WW-W\"",        "OSU-CISRC-4\\slash 87-TR9"},
  338.     {"\"W\"",            "Computer Science Report 100"},
  339.     {"\"X\"",            "TR/AB/3-43.7-3/AB"},
  340.     {(const char*)NULL,        NO_WARNING},
  341. };
  342.  
  343. int        main ARGS((int argc,char* argv[]));
  344. static void    process ARGS((const char *line_, MATCH_PATTERN patterns_[]));
  345.  
  346. #if NEW_STYLE
  347. int
  348. main(int argc, char* argv[])
  349. #else /* K&R style */
  350. int
  351. main(argc,argv)
  352. int argc;
  353. char* argv[];
  354. #endif /* NEW_STYLE */
  355. {
  356.     char line[MAXLINE];
  357.  
  358.     while (fgets(line,MAXLINE,stdin) != (char*)NULL)
  359.     {
  360.     char *p = strchr(line,'\n');
  361.     if (p != (char *)NULL)
  362.         *p = '\0';
  363.     process(line,number_patterns);
  364.     }
  365.  
  366.     exit (EXIT_SUCCESS);
  367.     return (EXIT_SUCCESS);
  368. }
  369.  
  370.  
  371. #if NEW_STYLE
  372. static void
  373. process(const char *line, MATCH_PATTERN patterns[])
  374. #else /* K&R style */
  375. static void
  376. process(line,patterns)
  377. const char *line;
  378. MATCH_PATTERN patterns[];
  379. #endif /* NEW_STYLE */
  380. {
  381.     int k;
  382.     for (k = 0; patterns[k].pattern != (const char*)NULL; ++k)
  383.     {
  384.     if (match_pattern(line,patterns[k].pattern) == YES)
  385.     {
  386.         if (patterns[k].message != NO_WARNING)
  387.         printf("%%%% [%-24s]: %s\n", line, patterns[k].message);
  388.         return;
  389.     }
  390.     }
  391.     printf("?? [%-24s]: Illegal value\n", line);
  392. }
  393. #endif /* TEST */
  394.